use std::collections::HashMap;

use egui_snarl::{NodeId, OutPinId};
use serde::{Deserialize, Serialize};

use crate::graph::{self, node::{Node, NodeTrait}, node_tree::{SnarlGenTree, SnarlTree}, value::{blackboard::BlackboardValue, Value}, Graph};

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct ExportNodeData {
    pub ty: String,
    pub childrens: Vec<usize>,
    pub inputs: Vec<ExportInputData>,
    pub outputs: Vec<ExportOutputData>,
}

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct ExportInputData {
    pub name: String,
    pub value: BlackboardValue,
}

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct ExportOutputData {
    pub name: String,
    pub value: BlackboardValue,
}

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct ExportGraphData {
    pub root: Option<usize>,
    pub nodes: Vec<ExportNodeData>,
}

struct BlackBoardCounter {
    cache: std::collections::HashMap<(NodeId, usize, Option<String>), String>,
    counter: usize,
}

impl BlackBoardCounter {
    fn new() -> Self {
        Self {
            cache: std::collections::HashMap::new(),
            counter: 0,
        }
    }

    fn get_name(&mut self, node_id: NodeId, index: usize, source:Option<String>) -> String {
        let key = (node_id, index, source);
        if let Some(name) = self.cache.get(&key) {
            return name.clone();
        }
        let name = format!("blackboard_{}", self.counter);
        self.counter += 1;
        self.cache.insert(key, name.clone());
        name
    }

    fn get_existing_name(&self, node_id: NodeId, index: usize, source:Option<String>) -> Option<String> {
        let key = (node_id, index, source);
        self.cache.get(&key).map(|name| name.clone())
    }
}

struct NodeIdIndexCounter {
    counter: usize,
    cache: std::collections::HashMap<(NodeId, Option<String>), usize>,
}

#[derive(Clone, Copy, Debug)]
enum NodeInex {
    New(usize),
    Old(usize),
}

impl NodeInex {
    fn index(&self) -> usize {
        match self {
            Self::New(index) => *index,
            Self::Old(index) => *index,
        }
    }

    fn is_new(&self) -> bool {
        match self {
            Self::New(_) => true,
            Self::Old(_) => false,
        }
    }
}

impl NodeIdIndexCounter {
    fn new() -> Self {
        Self {
            counter: 0,
            cache: std::collections::HashMap::new(),
        }
    }

    fn get_index(&mut self, node_id: NodeId, source:Option<String>) -> NodeInex {
        let key = (node_id, source);
        if let Some(index) = self.cache.get(&key) {
            return NodeInex::Old(*index);
        }
        let index = self.counter;
        self.counter += 1;
        self.cache.insert(key, index);
        NodeInex::New(index)
    }
}

fn parse_input_node_ref_data(out_pin:OutPinId, value: &Value, graph: &graph::Graph, blackboard_counter: &mut BlackBoardCounter, graph_cache:&mut GraphCache, work_dir:&str, source:Option<String>)->Result<BlackboardValue, String> {
    let out_node_id = out_pin.node;
    let out_node = graph.snarl.get_node(out_node_id).ok_or("Out node not found")?;
    match out_node {
        Node::Normal(_) => {
            let blackboard_name = blackboard_counter.get_name(out_node_id, out_pin.output, source.clone());
            Ok(BlackboardValue::Blackboard(blackboard_name, value.clone()))
        },
        Node::BlackboardInRef(_) => {
            let out_node_id = out_pin.node;
            let super_out_pin_id = graph.snarl.wires().find_map(|(out_pin, in_pin)|{
                if in_pin.node == out_node_id  {
                    Some(out_pin)
                } else {
                    None
                }
            }).ok_or("Super out pin not found")?;
            parse_input_node_ref_data(super_out_pin_id, value, graph, blackboard_counter, graph_cache, work_dir, source)
        },
        Node::CustomData(custom_data_node) => {
            let blackboard_name = custom_data_node.name.clone();
            Ok(BlackboardValue::Blackboard(blackboard_name, value.clone()))
        },
        Node::Constant(constant_node) => {
            let ref_constant = graph.constants.get(&constant_node.name).ok_or("Constant not found")?;
            Ok(BlackboardValue::Value(ref_constant.clone()))
        },
        Node::IncludeConstant(include_constant_node) => {
            let def_graph = graph_cache.get_graph_or_creat(work_dir, &include_constant_node.file)?;
            let ref_constant = def_graph.constants.get(&include_constant_node.name).ok_or("Constant not found")?;
            Ok(BlackboardValue::Value(ref_constant.clone()))
        },
        Node::IncludeCustomData(include_custom_data_node) => {
            let def_graph = graph_cache.get_graph_or_creat(work_dir, &include_custom_data_node.file)?;
            let ref_custom_data = def_graph.custom_datas.get(&include_custom_data_node.name).ok_or("Custom data not found")?;
            Ok(BlackboardValue::Value(ref_custom_data.clone()))
        },
        _ => Err("Invalid node type".to_string()),
    }
}

fn collect_node_inputs_data(target: NodeId, graph: &graph::Graph, blackboard_counter: &mut BlackBoardCounter
    , node_id_counter: &mut NodeIdIndexCounter, dest:&mut ExportGraphData, tree_data:&SnarlTree, cached_node_datas:&mut HashMap<(NodeId, Option<String>), ExportNodeData>
    , graph_cache:&mut GraphCache, work_dir:&str, source:Option<String>)->Result<usize, String>{
    let node = graph.snarl.get_node(target).ok_or("Node not found")?;
    match node {
        Node::Normal(normal_node) => {
            let index_data = node_id_counter.get_index(target, source.clone());
            if !index_data.is_new() {
                return Ok(index_data.index());
            }
            let index = index_data.index();
            let input_ref_info = graph.snarl.wires().filter(|(_, inpin)|{
                inpin.node == target
            }).map(|(outpin, inpin)|{
                (inpin.input, outpin)
            }).collect::<HashMap<_, _>>();
            let mut node_data = ExportNodeData {
                ty: normal_node.ty().to_string(),
                childrens: Vec::new(),
                inputs: Vec::new(),
                outputs: Vec::new(),
            };
            for (index, input_data) in normal_node.inputs.iter().enumerate() {
                //不需要处理Node类型的输入，因为在childrens中已经处理了
                if let Value::Node = input_data.value {
                    continue;
                }

                let name = input_data.name.clone();
                match input_ref_info.get(&index) {
                    Some(out_pin) => {
                        let blackboard_value = parse_input_node_ref_data(*out_pin, &input_data.value, graph, blackboard_counter, graph_cache, work_dir, source.clone())?;
                        node_data.inputs.push(ExportInputData {
                            name,
                            value: blackboard_value,
                        });
                    },
                    None => {
                        node_data.inputs.push(ExportInputData {
                            name,
                            value: BlackboardValue::Value(input_data.value.clone()),
                        });
                    },
                }
            }

            if let Some(childrens) = tree_data.nodes.get(&target).map(|node| node.children.clone()) {
                for child in childrens {
                    let child_index = collect_node_inputs_data(child, graph, blackboard_counter, node_id_counter, dest, tree_data, cached_node_datas, graph_cache, work_dir, source.clone())?;
                    node_data.childrens.push(child_index);
                }
            }

            cached_node_datas.insert((target, source.clone()), node_data.clone());

            Ok(index)
        },
        Node::Root(_) => {
            let childrens: Vec<NodeId> = tree_data.nodes.get(&target).map(|node| node.children.clone()).ok_or("Root node not found")?;
            let first_child = childrens.first().ok_or("Root node has no children")?;
            collect_node_inputs_data(first_child.clone(), graph, blackboard_counter, node_id_counter, dest, tree_data, cached_node_datas, graph_cache, work_dir, source.clone())
        },
        Node::ModuleRef(module_ref_node) => {
            let def_node_id = module_ref_node.ref_node_id;
            collect_node_inputs_data(def_node_id, graph, blackboard_counter, node_id_counter, dest, tree_data, cached_node_datas, graph_cache, work_dir, source.clone())
        },
        Node::IncludeModuleRef(include_module_ref_node) => {
            let mut path_buf = std::path::PathBuf::from(work_dir);
            path_buf.push(include_module_ref_node.file.clone());
            let def_graph = graph_cache.get_graph_or_creat(work_dir, &include_module_ref_node.file)?.clone();
            let new_tree_data = def_graph.snarl.gen_node_tree();
            let ref_node_id = def_graph.snarl.nodes_ids_data().find_map(|(node_id, data)|{
                if let Node::ModuleDef(ref def_node) = data.value {
                    if def_node.name == include_module_ref_node.ref_node_name {
                        Some(node_id)
                    } else {
                        None
                    }
                } else {
                    None
                }
            }).ok_or(&format!("Ref Node:{} Not Found In File:{}", include_module_ref_node.ref_node_name, include_module_ref_node.file))?;
            collect_node_inputs_data(ref_node_id, &def_graph, blackboard_counter, node_id_counter, dest, &new_tree_data, cached_node_datas, graph_cache, work_dir, Some(include_module_ref_node.file.clone()))

        },

        Node::ModuleDef(_) => {
            let childrens = tree_data.nodes.get(&target).map(|node| node.children.clone()).ok_or("Module def node not found")?;
            let first_child = childrens.first().ok_or("Module def node has no children")?;
            collect_node_inputs_data(first_child.clone(), graph, blackboard_counter, node_id_counter, dest, tree_data, cached_node_datas, graph_cache, work_dir, source.clone())
        },
        _ => Err("Invalid node type".to_string()),
    }
}

fn generate_node_data(target: NodeId, graph: &graph::Graph, blackboard_counter: &mut BlackBoardCounter
    , node_id_counter: &mut NodeIdIndexCounter, dest:&mut ExportGraphData, tree_data:&SnarlTree
    , cached_node_datas:&mut HashMap<(NodeId, Option<String>), ExportNodeData>, graph_cache:&mut GraphCache, work_dir:&str, source:Option<String>)->Result<(), String>{
    let index = collect_node_inputs_data(target, graph, blackboard_counter, node_id_counter, dest, tree_data, cached_node_datas, graph_cache, work_dir, source.clone())?;
    //展开全部的Output
    let mut before_sort_nodes = Vec::new();

    for ((node_id, src), value) in cached_node_datas.iter_mut() {
        let graph = if let Some(src) = src {
            graph_cache.get_graph(src).ok_or("Graph not found")?
        } else {
            graph
        };
        let node = graph.snarl.get_node(*node_id).ok_or("Node not found")?;
        let outputs = node.outputs();
        for (index, output) in outputs.iter().enumerate() {
            match output.value {
                Value::Node => {
                    continue;
                },
                _ => {
                    if let Some(key) = blackboard_counter.get_existing_name(node_id.clone(), index, src.clone()) {
                        let name = output.name.clone();
                        value.outputs.push(ExportOutputData {
                            name,
                            value: BlackboardValue::Blackboard(key, output.value.clone()),
                        });
                    }
                },
            };
        }

        before_sort_nodes.push((value.clone(), node_id_counter.get_index(node_id.to_owned(), src.to_owned())));
    }

    before_sort_nodes.sort_by(|a, b| {
        let a = a.1.index();
        let b = b.1.index();
        a.cmp(&b)
    });

    dest.nodes = before_sort_nodes.into_iter().map(|(node, _)| node).collect();

    dest.root = Some(index);

    Ok(())
}

struct GraphCache {
    cache: HashMap<String, Graph>,
}

impl GraphCache {
    fn new() -> Self {
        Self {
            cache: HashMap::new(),
        }
    }

    fn get_graph_or_creat(&mut self, work_dir: &str, file: &str) -> Result<&Graph, String> {
        if self.cache.contains_key(file) {
            return Ok(self.cache.get(file).unwrap());
        }
        
        let mut path_buf = std::path::PathBuf::from(work_dir);
        path_buf.push(file);
        
        let graph = Graph::load_from_path(path_buf).map_err(|e| format!("Load graph failed: {}", e))?;
        
        let graph = self.cache.entry(file.to_string()).or_insert(graph);
        
        Ok(graph)
    }

    fn get_graph(&self, file: &str) -> Option<&Graph> {
        self.cache.get(file)
    }

}

pub fn generate_export_graph_data(graph: &graph::Graph, work_dir:String) -> Result<ExportGraphData, String> {
    let mut result = ExportGraphData {
        root: None,
        nodes: Vec::new(),
    };
    
    let Some(root_node) = graph.snarl.nodes_ids_data().find_map(|(node_id, data)|{
        if let Node::Root(_) = data.value {
            Some(node_id)
        } else {
            None
        }
    }) else {
        return Err("Root node not found".to_string());
    };

    let tree_data = graph.snarl.gen_node_tree();
    let mut node_id_index_counter = NodeIdIndexCounter::new();
    let mut blackboard_counter = BlackBoardCounter::new();
    let mut cached_node_datas: HashMap<(NodeId, Option<String>), ExportNodeData> = HashMap::new();
    let mut graph_cache = GraphCache::new();
    generate_node_data(root_node, graph, &mut blackboard_counter, &mut node_id_index_counter, &mut result, &tree_data, &mut cached_node_datas, &mut graph_cache, &work_dir, None)?;

    Ok(result)
}
